// Simple command-line kernel monitor useful for
// controlling the kernel and exploring the system interactively.

#include <inc/stdio.h>
#include <inc/string.h>
#include <inc/memlayout.h>
#include <inc/assert.h>
#include <inc/x86.h>
#include <inc/mmu.h>

#include <kern/console.h>
#include <kern/monitor.h>
#include <kern/kdebug.h>
#include <kern/trap.h>
#include <kern/pmap.h>
#include <kern/env.h>

#define CMDBUF_SIZE	80	// enough for one VGA text line


struct Command {
	const char *name;
	const char *desc;
	// return -1 to force monitor to exit
	int (*func)(int argc, char** argv, struct Trapframe* tf);
};

static struct Command commands[] = {
    { "help", "Display this list of commands", mon_help },
    { "kerninfo", "Display information about the kernel", mon_kerninfo },
    { "backtrace", "Display the stack info", mon_backtrace},
    { "changecolor", "change the color", mon_changecolor},
    { "sm", "show physical mappings", mon_showmappings},
    { "per","change permission", mon_changeper},
    { "dm", "dump memory", mon_dumpmem},
    { "pagealloc", "alloc a page", mon_pagealloc },
    { "pagestatus", "see a page's status", mon_pagestatus},
    { "pagefree", "free a page", mon_pagefree},
    { "cont", "continue step a instruction", mon_continue}
};
#define NCOMMANDS (sizeof(commands)/sizeof(commands[0]))

unsigned read_eip();

/***** Implementations of basic kernel monitor commands *****/

int
mon_help(int argc, char **argv, struct Trapframe *tf)
{
	int i;

	for (i = 0; i < NCOMMANDS; i++)
		cprintf("%s - %s\n", commands[i].name, commands[i].desc);
	return 0;
}

int
mon_kerninfo(int argc, char **argv, struct Trapframe *tf)
{
	extern char _start[], etext[], edata[], end[];

	cprintf("Special kernel symbols:\n");
	cprintf("  _start %08x (virt)  %08x (phys)\n", _start, _start - KERNBASE);
	cprintf("  etext  %08x (virt)  %08x (phys)\n", etext, etext - KERNBASE);
	cprintf("  edata  %08x (virt)  %08x (phys)\n", edata, edata - KERNBASE);
	cprintf("  end    %08x (virt)  %08x (phys)\n", end, end - KERNBASE);
	cprintf("Kernel executable memory footprint: %dKB\n",
            (end-_start+1023)/1024);
	return 0;
}

int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
    // Your code here.
    cprintf("Stack backtrace:\n");
    int first_ebp = read_ebp(); //get ebp
    while (first_ebp != 0)
	{
	    cprintf("ebp %08x  eip %08x  args %08x %08x %08x %08x %08x\n", first_ebp, *((int *)first_ebp + 1), 
		    *((int *)first_ebp + 2), *((int *)first_ebp + 3), *((int *)first_ebp + 4), *((int *)first_ebp + 5), *((int *)first_ebp + 6));
	    struct Eipdebuginfo info;
	    debuginfo_eip(*((int *)first_ebp + 1), &info);
	    cprintf("       %s:%d: %.*s+%u\n", info.eip_file, info.eip_line, info.eip_fn_namelen, info.eip_fn_name, 
		    *((int *)first_ebp + 1)-info.eip_fn_addr);
	    first_ebp = *(int *)first_ebp;
        //cprintf("%x\n",first_ebp);
	}
    return 0;
}

int
mon_changecolor(int argc, char **argv, struct Trapframe *tf)
{//changecolor [-hl] color ,-hl to set highlight.
    if(argc == 1)
	{
	    change_color(1, 0);
	    return 1;
	}
    if(argc == 2) //without highlight
	{
	    if(!strcmp(argv[1],"red"))
		change_color(4, 0);
	    else if(!strcmp(argv[1],"blue"))
		change_color(2, 0);
	    else if(!strcmp(argv[1],"green"))
		change_color(3, 0);
	    else
		{
		    if(!strcmp(argv[1],"-hl"))
			{
			    change_color(1, 1);
			    return 1;
			}
		    cprintf("color no defined, back to default.\n");
		    change_color(1, 0);
		}
	}
    else if (argc == 3)//highlight mode
	{
	    if(strcmp(argv[1],"-hl"))
		{
		    cprintf("parameter error, back to default.\n");
		    change_color(1,0);
 		    return 1;
		}
	    if(!strcmp(argv[2],"red"))
		change_color(4, 1);
	    else if(!strcmp(argv[2],"blue"))
		change_color(2, 1);
	    else if(!strcmp(argv[2],"green"))
		change_color(3, 1);
	    // else
	    // {
	    //cprintf("color no defined, back to default.\n");
	    //     change_color(1, 1);
	    // }
	}
    
    return 1;
}
int
mon_showmappings(int argc, char **argv, struct Trapframe *tf)
{
    extern pde_t * boot_pgdir;
    pde_t * pgdir = boot_pgdir;
    if(argc > 3)
	{
	    cprintf("wrong parameters\n");
	    return 0;
	}
    if(argc == 2)
	{
	    char * name = hex_formatter(argv[1]);
	    cprintf("%s\n",name);
	    unsigned int num = char_int(name, 16);
	    cprintf("%x\n",num);
	    pte_t * entry = pgdir_walk(pgdir, (void *)num, 1);
	    if((*entry == 0))
		{
		    cprintf("not mapped\n");
		    return 0;
		}
        
	    cprintf("vir addr \t   phy addr\tP\tW\tU\n");
	    //  if(!(*entry == 0))
	    cprintf("0x%08x \t 0x%08x\t%d\t%d\t%d\n", num ,PTE_ADDR(*entry),*entry & 1,
		    (*entry & 2) >> 1, (*entry & 4) >> 2);
	}
    if(argc == 3)
	{
	    char * start = hex_formatter(argv[1]);
	    unsigned int starta = char_int(start, 16);
	    char * end = hex_formatter(argv[2]);
	    unsigned int enda = char_int(end, 16);
	    if(enda < starta)
		{
		    cprintf("parameters wrong\n");
		    return 0;
		}
	    // cprintf("vir addr \t   phy addr \t refer \n");
	    cprintf("vir addr \t   phy addr\tP\tW\tU\n");
	    int i= starta;
	    for(; i <= enda; i += PGSIZE)
		{
		    pte_t * entry = pgdir_walk(pgdir, (void *)i, 1);
		    //  pte_t * temp;
		    // struct Page* page = page_lookup(pgdir, (void*)i, 0);
		    if(!*entry == 0)
			cprintf("0x%08x \t 0x%08x\t%d\t%d\t%d\n", i ,PTE_ADDR(*entry),
				*entry & 1, (*entry & 2) >> 1, (*entry & 4) >> 2);
		}
	}
    
    return 0;
}

int mon_changeper(int argc, char **argv, struct Trapframe *tf)
{
    extern pde_t * boot_pgdir;
    pde_t * pgdir = boot_pgdir;
    char * start = hex_formatter(argv[2]);
    unsigned int addr = char_int(start, 16);
    pte_t * entry = pgdir_walk(pgdir, (void *)addr, 1);
    if(*entry == 0)
	{
	    cprintf("invalid addr\n");
	    return 0;
	}
    if(strcmp(argv[1], "-s") == 0)
	{
	    switch (argv[3][0]){
	    case 'p': *entry = *entry | PTE_P; break;
	    case 'w': *entry = *entry | PTE_W; break;
	    case 'u': *entry = *entry | PTE_U; break;
	    case 't': *entry = *entry | PTE_PWT; break;
	    case 'c': *entry = *entry | PTE_PCD; break;
	    case 'a': *entry = *entry | PTE_A; break;
	    case 'd': *entry = *entry | PTE_D; break;
	    case 'z': *entry = *entry | PTE_MBZ; break;
	    default: break;
	    }
                
	}
    else if(strcmp(argv[1], "-c") == 0)
	{
	    switch (argv[3][0]){
	    case 'p': *entry = *entry & ~PTE_P; break;
	    case 'w': *entry = *entry & ~PTE_W; break;
	    case 'u': *entry = *entry & ~PTE_U; break;
	    case 't': *entry = *entry & ~PTE_PWT; break;
	    case 'c': *entry = *entry & ~PTE_PCD; break;
	    case 'a': *entry = *entry & ~PTE_A; break;
	    case 'd': *entry = *entry & ~PTE_D; break;
	    case 'z': *entry = *entry & ~PTE_MBZ; break;
	    default: break;
	    }
	}
    return 0;
}
//dump memory
int mon_dumpmem(int argc, char **argv, struct Trapframe *tf)
{
    //dump -p|-v add
    //  cprintf("%d",argc);
    if(argv[1][1] != 'p' && argv[1][1] !='v' && argc != 4)
    {
        cprintf("wrong parameter\n");
        return 0;
    }
    uintptr_t a = char_int(hex_formatter(argv[2]), 16);
    uintptr_t b = char_int(hex_formatter(argv[3]), 16);
    cprintf("begin:%x\t", a);
    cprintf("end:%x\n", b);
    if(argv[1][1] == 'p')
    {
        a = (uintptr_t)KADDR(a);
        b = (uintptr_t)KADDR(b);
    }
    while(a <= b)
    {
        if(page_lookup(PGADDR(PDX(VPT),PDX(VPT),0), (void *)a, NULL)== NULL)
        {
            cprintf("not mapped\n");
            a = ROUNDUP(a, PGSIZE);
            // continue;
        }
        int i= 0;
        for(; i < 8; i++ , a ++)
        {
            if(a > b)
                break;
            cprintf("%02x\t", *(unsigned char *)a);
        }
       
        cprintf("\n");

    }
    return 0;
}
int mon_pagealloc(int argc, char **argv, struct Trapframe *tf)
{
    if(argc != 1)
    {
        cprintf("parameters wrong.\n");
        return 0;
    }
    struct Page * page;
    if(page_alloc(&page) != 0)
    {
        cprintf("no more free memory!\n");
        return 0;
    }
    page -> pp_ref = 1;
    cprintf("\n\t0x%08x\n", page2pa(page));
    return 1;

}
int mon_pagestatus(int argc, char **argv, struct Trapframe *tf)
{
    if(argc != 2)
    {
        cprintf("parameter wrong!\n");
        return 0;
    }
    uintptr_t a = char_int(hex_formatter(argv[1]), 16);
    struct Page * b = pa2page(a);
    if(b -> pp_ref != 0)
    {
        cprintf("allocated.\n");
        return 1;
    }
    else
        cprintf("free.\n");
    return 1;
}
int mon_pagefree(int argc, char **argv, struct Trapframe *tf)
{
    if(argc != 2)
    {
        cprintf("parameter wrong!\n");
        return 0;
    }
    uintptr_t a = char_int(hex_formatter(argv[1]), 16);
    struct Page * b = pa2page(a);
    b -> pp_ref = 0;
    page_free(b);
    return 1;
}
int mon_continue(int argc, char **argv, struct Trapframe *tf)
{
    curenv -> env_tf.tf_eflags |= 1 << 8;
    env_run(curenv);
	return 0;
}
/***** Kernel monitor command interpreter *****/

#define WHITESPACE "\t\r\n "
#define MAXARGS 16

static int
runcmd(char *buf, struct Trapframe *tf)
{
	int argc;
	char *argv[MAXARGS];
	int i;

	// Parse the command buffer into whitespace-separated arguments
	argc = 0;
	argv[argc] = 0;
	while (1) {
		// gobble whitespace
		while (*buf && strchr(WHITESPACE, *buf))
			*buf++ = 0;
		if (*buf == 0)
			break;

		// save and scan past next arg
		if (argc == MAXARGS-1) {
			cprintf("Too many arguments (max %d)\n", MAXARGS);
			return 0;
		}
		argv[argc++] = buf;
		while (*buf && !strchr(WHITESPACE, *buf))
			buf++;
	}
	argv[argc] = 0;

	// Lookup and invoke the command
	if (argc == 0)
		return 0;
	for (i = 0; i < NCOMMANDS; i++) {
		if (strcmp(argv[0], commands[i].name) == 0)
			return commands[i].func(argc, argv, tf);
	}
	cprintf("Unknown command '%s'\n", argv[0]);
	return 0;
}

void
monitor(struct Trapframe *tf)
{
	char *buf;

	cprintf("Welcome to the JOS kernel monitor!\n");
	cprintf("Type 'help' for a list of commands.\n");

	if (tf != NULL)
		print_trapframe(tf);

	while (1) {
		buf = readline("K> ");
		if (buf != NULL)
			if (runcmd(buf, tf) < 0)
				break;
	}
}

// return EIP of caller.
// does not work if inlined.
// putting at the end of the file seems to prevent inlining.
unsigned
read_eip()
{
	uint32_t callerpc;
	__asm __volatile("movl 4(%%ebp), %0" : "=r" (callerpc));
	return callerpc;
}
